// src/utils/tts-xf.ts                                                             // 本文件：科大讯飞 TTS 前端直连工具（流式播放版）
// 纯前端实现：科大讯飞私有域 TTS（流式边播，mp3）                                   // 场景：无后端签名服务，直接在浏览器里签名并调用 WS
// 风险提示：在前端保存 apiSecret 会泄露，请仅在测试/内网使用。                        // 生产必须后端签名！

// ========== Base64 编解码 ==========
export function b64encode(str: string) {                                                  // 将任意 JS 字符串按 UTF-8 编码再转为 base64
  const utf8 = new TextEncoder().encode(str);                                      // TextEncoder 把字符串编码成 Uint8Array（UTF-8）
  return b64encodeBytes(utf8.buffer);                                              // 复用下方字节数组→base64 的方法
}
export function b64encodeBytes(buf: ArrayBuffer) {                                        // 将 ArrayBuffer 转为 base64 字符串
  const bytes = new Uint8Array(buf);                                               // 把缓冲区视为字节数组
  let binary = "";                                                                 // 暂存二进制字符串
  for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]); // 逐字节拼接为二进制字符串
  return btoa(binary);                                                              // 使用 btoa 将二进制字符串编码为 base64
}

// ========== HMAC-SHA256（Web Crypto） ==========
async function hmacSHA256(key: string, msg: string) {                              // 使用 WebCrypto 计算 HMAC-SHA256 签名
  const enc = new TextEncoder();                                                    // 用于把字符串转成 Uint8Array
  const cryptoKey = await crypto.subtle.importKey(                                 // 导入原始 key 为 HMAC 密钥
    "raw",                                                                         // 原始原料格式
    enc.encode(key),                                                                // key 的字节表示
    { name: "HMAC", hash: "SHA-256" },                                             // 算法：HMAC-SHA256
    false,                                                                          // 不允许导出密钥
    ["sign"]                                                                        // 权限：仅签名
  );
  const sig = await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(msg));        // 对消息进行签名，返回 ArrayBuffer
  return sig;                                                                       // 返回二进制签名
}

// ========== 生成已签名的 WS URL ==========
export async function buildSignedWsUrl(opts: {                                     // 根据 apiKey/apiSecret 构造"带签名"的 WS URL
  host?: string;
  path?: string;
  apiKey: string;
  apiSecret: string;
}) {
  const host = opts.host ?? "cbm01.cn-huabei-1.xf-yun.com";                       // 目标域名（按你的账号地域可能不同）
  const path = opts.path ?? "/v1/private/mcd9m97e6";                              // 目标路径（私有接口路径，按实际控制台为准）
  
  console.log("[XF-TTS] Building signed URL with host:", host, "path:", path);
  const date = new Date().toUTCString();                                           // RFC1123 格式时间，用于签名串

  const signatureOrigin = `host: ${host}\ndate: ${date}\nGET ${path} HTTP/1.1`;   // 按官方规则拼“签名原文”
  const signatureBin = await hmacSHA256(opts.apiSecret, signatureOrigin);           // 用 apiSecret 对原文做 HMAC-SHA256
  const signature = b64encodeBytes(signatureBin);                                   // 将二进制签名转为 base64

  const authorizationOrigin =                                                     // 组装 authorization 明文串
    `api_key="${opts.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
  const authorization = b64encode(authorizationOrigin);                            // 对 authorization 明文再做一次 base64
  const url = `wss://${host}${path}?authorization=${encodeURIComponent(authorization)}&date=${encodeURIComponent(date)}&host=${encodeURIComponent(host)}`; // 拼接最终 wss URL
  console.log("[XF-TTS] Built signed URL", { host, path, date });                 // 打印签名要素，便于排查时间/域名/路径问题
  return url;
}

// ========== 构造一次性合成请求体（x5 发音人，不带 oral） ==========
export function buildOncePayload(appId: string, text: string, vcn = "x5_lingfeiyi_flow") { // 构造一次性（status=2）TTS 请求体
  const textB64 = b64encode(text);                                                 // 将原始文本按 UTF-8→base64，兼容部分网关的传输要求
  const validB64 = /^[A-Za-z0-9+/=]+$/.test(textB64);                              // 粗检 base64 字符是否只包含合法字符集
  if (!validB64) {                                                                 // 正常不会发生，仅保护性日志
    console.warn("[XF-TTS] text base64 contains invalid chars (should not happen)");
  }
  return {                                                                         // 返回完整 JSON 请求体
    header: { app_id: appId, status: 2 },                                          // header：携带 app_id，status=2 代表一次性发送即结束
    parameter: {                                                                   // parameter：能力配置
      tts: {                                                                       // tts：发音控制项
        vcn,                                                                       // vcn：发音人（需已开通）
        speed: 50,                                                                 // speed：语速 0~100
        volume: 50,                                                                // volume：音量 0~100
        pitch: 50,                                                                 // pitch：音调 0~100
        bgs: 0, reg: 0, rdn: 0, rhy: 0,                                           // 其他控制项，按默认 0
        audio: {                                                                   // 输出音频参数
          encoding: "lame",                                                       // encoding：lame=mp3
          sample_rate: 24000,                                                      // 采样率：24k
          channels: 1,                                                             // 单声道
          bit_depth: 16,                                                           // 16bit 位深
          frame_size: 0,                                                           // 帧大小：默认 0
        },
      },
    },
    payload: {                                                                     // payload：输入文本
      text: {                                                                      // text：文本对象
        encoding: "utf8",                                                         // encoding：声明原字符集为 utf8
        compress: "raw",                                                          // compress：不压缩
        format: "plain",                                                          // format：纯文本
        status: 2,                                                                 // 一次性发送：直接置 2（结束）
        seq: 0,                                                                    // 序号固定 0
        text: textB64,                                                             // 实际内容：base64(utf8(text))
      },
    },
  };
}

// ========== 基于 MediaSource 的 MP3 流式播放器 ==========
class Mp3StreamPlayer {
  private mediaSource: MediaSource;                            // MSE 媒体源对象，用于动态喂入音频数据
  private sourceBuffer: SourceBuffer | null = null;            // 与 mediaSource 绑定的音频缓冲区
  private audio: HTMLAudioElement;                             // 实际用来播放的 <audio> 元素
  private queue: Uint8Array[] = [];                           // 待写入 SourceBuffer 的二进制分片队列
  private opened = false;                                      // MSE 是否已触发 sourceopen（可写入）
  private ended = false;                                       // 是否已接收完毕（status=2）

  constructor() {
    this.mediaSource = new MediaSource();                      // 创建 MSE 实例
    const url = URL.createObjectURL(this.mediaSource);         // 为 MSE 生成一个 blob URL
    this.audio = new Audio(url);                               // 让 <audio> 播放该 URL，后续往 MSE 追加数据即可边播
    this.audio.addEventListener("canplay", () => {             // 当音频缓冲到可播放
      // 如果还没播放，尝试播放（用户已交互，应该可以自动播放）
      if (this.audio.paused) {
        this.audio.play().catch(() => {
          // 静默处理播放失败
        });
      }
    });
    this.audio.addEventListener("error", (e) => {            // 播放器错误
      console.error("[XF-TTS] Audio playback error:", this.audio.error);
    });
    this.mediaSource.addEventListener("sourceopen", () => {    // MSE 打开后才允许添加 SourceBuffer
      console.log("[XF-TTS] MSE sourceopen");
      this.sourceBuffer = this.mediaSource.addSourceBuffer("audio/mpeg"); // 指定音频类型：MP3
      this.sourceBuffer.mode = "sequence";                      // 顺序追加，避免乱序时间戳
      this.sourceBuffer.addEventListener("updateend", () => this.flush()); // 一次写入结束后继续写下一片
      this.opened = true;                                       // 标记可写
      this.flush();                                             // 尝试把队列中已有分片写入
    });
  }

  async play() {                                               // 主动触发播放（可能被浏览器策略拒绝）
    try {
      await this.audio.play();
    } catch {
      // 静默处理，等待音频数据后自动播放
    }
  }

  appendBase64(b64: string) {                                  // 追加一段 base64(mp3) 到缓冲队列
    const bin = atob(b64);                                      // base64 → 二进制字符串
    const buf = Uint8Array.from(bin, (c) => c.charCodeAt(0));   // 二进制字符串 → Uint8Array
    this.queue.push(buf);                                       // 入队，等待写入
    this.flush();                                               // 尝试立即写入（若可写且不在 updating）
  }

  private flush() {                                            // 将队列中的分片写入 SourceBuffer
    if (!this.opened || !this.sourceBuffer) return;           // MSE 未就绪或尚无缓冲区
    if (this.sourceBuffer.updating) return;                   // 正在写入，等待 updateend 再写
    const next = this.queue.shift();                           // 取出下一个待写入的分片
    if (!next) {                                               // 队列空
      if (this.ended && this.mediaSource.readyState === "open") {
        try { 
          this.mediaSource.endOfStream();
        } catch { /* ignore */ }
      }
      return;
    }
    try {
      const ab = new ArrayBuffer(next.byteLength);
      new Uint8Array(ab).set(next);
      this.sourceBuffer.appendBuffer(ab);
    } catch (e) {
      console.error("[XF-TTS] appendBuffer error:", e);
    }
  }

  markEnd() {                                                  // 标记所有分片接收完毕
    this.ended = true;                                         // 置结束标志
    this.flush();                                              // 若队列已空则 endOfStream()
  }

  element() {                                                  // 暴露内部 audio 元素（如需挂载到 DOM）
    return this.audio;                                         // 返回 <audio>
  }
}

// ========== 前端直连：流式边播 ==========
export async function speakStreamFrontOnly(opts: {            // 公开方法：前端直连 TTS，边收边播
  apiKey: string;                                            // 讯飞 API Key（前端暴露仅测试用）
  apiSecret: string;                                         // 讯飞 API Secret（同上）
  appId: string;                                             // APPID
  text: string;                                              // 要合成的文本
  vcn?: string;                                              // 可选：发音人
}) {
  const wsUrl = await buildSignedWsUrl({                     // 用 key/secret 生成带签名的 WS 地址
    apiKey: opts.apiKey,
    apiSecret: opts.apiSecret,
  });

  return new Promise<void>((resolve, reject) => {            // 返回 Promise，便于上层 await/then
    console.log("[XF-TTS] Connecting WS (stream)...", { url: wsUrl }); // 打印连接地址
    const ws = new WebSocket(wsUrl);                         // 建立 WebSocket 连接
    const player = new Mp3StreamPlayer();                    // 创建流式播放器实例
    let watchdog: number | undefined;                        // 看门狗定时器 id

    ws.onopen = () => {                                      // 连接成功
      console.log("[XF-TTS] WS open (stream)");
      const payload = buildOncePayload(opts.appId, opts.text, opts.vcn); // 构造一次性请求体（status=2）
      const body = JSON.stringify(payload);                  // 序列化为 JSON 字符串
      ws.send(body);                                         // 发出请求
      // 提前尝试播放（即使被浏览器策略拦截，有数据后会自动播放）
      player.play().catch(() => {
        // 静默处理，等待音频数据到达后自动播放
      });
      watchdog = window.setTimeout(() => {                   // 45s 超时保护（避免一直卡住）
        console.error("[XF-TTS] Timeout waiting response (stream 45s)");
        try { ws.close(); } catch { /* noop */ }
        reject(new Error("XF-TTS timeout(stream)"));
      }, 45000);
    };

    ws.onmessage = (evt) => {                                // 收到服务端数据帧
      try {
        // 尝试解析JSON
        let msg: any;
        try {
          msg = JSON.parse(evt.data);
        } catch (parseErr) {
          // 如果不是JSON，可能是二进制数据，忽略
          console.warn("[XF-TTS] Received non-JSON data, ignoring");
          return;
        }
        
        // 科大讯飞TTS返回格式：{header: {code, status, sid}, payload: {audio: {audio: base64}}}
        const code = msg?.header?.code;
        const status = msg?.header?.status;
        const message = msg?.header?.message;
        
        // 提取音频数据（可能有多个路径）
        let b64 = msg?.payload?.audio?.audio;
        if (!b64) b64 = msg?.payload?.data?.audio?.audio;
        if (!b64) b64 = msg?.data?.audio?.audio;
        if (!b64) b64 = msg?.audio;

        // 检查错误码
        if (typeof code !== "undefined" && code !== 0) {
          console.error("[XF-TTS] Server error:", { code, message, msg });
          if (watchdog) window.clearTimeout(watchdog);
          reject(new Error(`XF-TTS error: ${message || `code ${code}`}`));
          return;
        }

        // 如果有音频数据，添加到播放器
        if (b64 && b64.length > 0) {
          player.appendBase64(b64);
        }
        
        // 检查是否结束
        if (status === 2) {
          if (watchdog) window.clearTimeout(watchdog);
          player.markEnd();
          try { ws.close(); } catch { /* noop */ }
          resolve();
        }
      } catch (e) {
        console.error("[XF-TTS] Message processing error:", e);
      }
    };

    ws.onerror = (e) => {                                    // WS 错误（网络/鉴权等）
      if (watchdog) window.clearTimeout(watchdog);           // 清理看门狗
      console.error("[XF-TTS] ws error(stream):", e);        // 打印错误
      reject(e);                                             // 结束 Promise
    };
  });
}
